
/* GCSx
** DIALOG.H
**
** Dialog boxes, and basic dialog elements
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_DIALOG_H_
#define __GCSx_DIALOG_H_

// A dialog Window collects widget Windows and is itself the client area
// of a FrameWindow.
class Widget;
class Dialog : public WindowCollection, public Window {
protected:
    std::string title;
    int lastButtonId;
    int open;
    int openModal;
    int nextTabId;
    class FrameWindow* myFrame;
    int autoApply; // If widgets apply on every setting change
    int noAutoClose; // Prevent ESC key and Enter key
    int inSiblingModify; // Are we in the middle of siblingModified()?
    int hasTitlebar;
    int isToolPanel;
    
    // For arrangeRow() and makePretty()
    int lastWidgetArranged;
    int nextWidgetY;
    
    // Apply/save settings
    // Don't call this directly- use doAction()
    void applySettings();

    // Load settings
    // Called from runModal and runWindowed
    void loadSettings();
    
    // Dialog sizing constants
    enum {
        // Separation between elements
        GUI_DIALOG_SEPHEIGHT = 5,
        GUI_DIALOG_SEPWIDTH = 2, // Used by arrangeRow() only
        // Separation before button row
        GUI_DIALOG_BUTTONSEPHEIGHT = 10,
        // Separation between multiple checks or radios
        GUI_DIALOG_CHECKSEPHEIGHT = 0, // Used by makePretty() only
        // Separation between two columns
        GUI_DIALOG_GUTTERWIDTH = 5, // Used by makePretty() only
        // Margins on edges- normal dialog
        GUI_DIALOG_TOPMARGIN = 5,
        GUI_DIALOG_BOTTOMMARGIN = 5,
        GUI_DIALOG_LEFTMARGIN = 5,
        GUI_DIALOG_RIGHTMARGIN = 5,
        // Margins on edges- tool panel dialog
        GUI_PANEL_TOPMARGIN = 2,
        GUI_PANEL_BOTTOMMARGIN = 2,
        GUI_PANEL_LEFTMARGIN = 3,
        GUI_PANEL_RIGHTMARGIN = 1,
        // Max number of columns/rows we support
        GUI_DIALOG_NUMCOL = 10, // Used by makePretty() only
        GUI_DIALOG_NUMROW = 20, // Used by makePretty() only
    };

    // Opens this dialog, but without assigning it as a child window anywhere
    void runAsPanel();

public:
    enum ButtonAction {
        // "DEFAULT" means "whatever the button is set to do" and should only be used
        // as a return value for verifyEntry(), not as an actual button action
        BUTTON_DEFAULT,
        BUTTON_NOTHING,
        BUTTON_OK,
        BUTTON_APPLY,
        BUTTON_CANCEL,
        BUTTON_LASTACT = BUTTON_CANCEL,
    };

    enum DialogAction {
        DIALOGACTION_CHECK,
        DIALOGACTION_UNCHECK,
        DIALOGACTION_RADIO,
        DIALOGACTION_TEXTCHANGE,
    };
    
    Dialog(const std::string& dTitle, int dAutoApply = 0, int dHasTitlebar = 1, int toolPanel = 0);
    virtual ~Dialog();
    void setTitle(const std::string& newTitle);

#ifndef NDEBUG
    const char* debugDump() const;
#endif
    
    virtual int event(int hasFocus, const SDL_Event* event);
    void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
    void resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp);
    const char* tooltip(int xPos, int yPos) const;

    WindowType windowType() const;
    WindowSort windowSort() const;
    CommandSupport supportsCommand(int code) const;
    
    // For outside code to find widgets to edit properties, etc.
    // If it finds a container widget, it returns the inner widget
    // which = 1 for second widget, etc. May not be in original order added!
    Widget* findWidget(int id, int which = 0);
    
    // By default, dialogs do NOT want to be deleted, most are settings dialogs
    // you set up once and bring back over and over
    virtual int wantsToBeDeleted() const;
    
    // Handles calling verifyEntry(0), etc.
    // You shouldn't call this directly, it's used by frame windows only
    int attemptClose();
    
    // Validates that all current entry is OK and/or runs custom code for a
    // button, return an action. buttonId of 0 for closing
    // the dialog via [X]. The default code for this merely runs entryValid() on
    // all widgets if buttonType is OK or APPLY.
    virtual ButtonAction verifyEntry(int buttonId, ButtonAction buttonType);
    
    // Remove all widgets; allows you to readd widgets
    // Must be closed when called!
    void clearWidgets();

    // Add a widget to this dialog; if fails, widget is not owned by us
    // If successful, we will handle deleting widget unless it's not set as wantsToBeDeleted()
    // Typically called by widget from addTo()
    void addWidget(Widget* newWidget);
    
    // Rearrange size of dialog and all new widgets automatically
    // Call this after each chunk or when all done
    // If you wish to arrange as a table, increase maxColumns
    // Puts static text in column 1 right-aligned, all other widgets in columns 2 onwards
    // If you simply want all widgets to flow into all columns, set pureFlow to true
    // If you want all static text right-aligned, set staticRight to true
    // (this only matters for pureFlow mode)
    // ALWAYS centers a final row of buttons, in all circumstances
    void makePretty(int maxColumns = 2, int vertCenter = 1, int pureFlow = 0, int staticRight = 0);

    // All widgets added since last call to this are arranged in a single row
    // Call this after each row
    void arrangeRow(int vertCenter = 1, int leftColumnSize = -1, int staticRight = 0);
    
    // Creates a FrameWindow, run this dialog, return the button code pressed
    // Returns 0 if ESC/[X] and no CANCEL button
    int runModal(int xPos = -1, int yPos = -1);
    
    // Creates a FrameWindow, add dialog to desktop, return with dialog still open
    void runWindowed(int xPos = -1, int yPos = -1);
    
    // Actually handles an action- call with a constant other than DEFAULT
    // Returns true if this window was closed, possibly deleted
    int doAction(ButtonAction buttonType);

    // First control- used on dialog init
    virtual void firstControl();

    // Next and previous control, optionally limited to controls with a given ID
    // Can do next from a control other than current if desired
    // Returns true if control was switched.
    int nextControl(int id = -1, const Widget* from = NULL);
    int prevControl(int id = -1);
    
    // Select first button with given action; returns true if found
    int buttonControl(ButtonAction action);
    void currentFocusDoAction();
    
    // Select control based on hotkey AND ACTIVATE; returns true if found
    int hotkeyControl(char keypress);
    
    // Called whenever a child is modified, propogates to all other controls
    //  (via siblingModified())
    // If this causes another widget to modify, it will NOT propogate
    // A button press doesn't call this
    // focus or other purely-visual or cursor changes don't call this
    // load() shouldn't call this
    virtual void childModified(Window* modified);
};

class Widget : public Window {
protected:
    std::string label;
    int underline;
    int disabled;
    int haveFocus;
    int tabId;
    char shortcut;
    const char* tip;
    
    // Internal ID, internal use includes grouping radio buttons and pass to
    // verifyEntry and afterChange in Dialog class. Internal ID should be >= 0
    int id;
    
    // (where we store the setting when the dialog closes)
    void* setting;
    
    // sets label, underline
    void setLabel(const std::string& newLabel);
    
public:
    Widget(int wId, const std::string& wLabel, void* wSetting);
    virtual ~Widget();

    // (mostly a shortcut)
    Dialog* myParent();    

    void setToolTip(const char* wTip);
    virtual const char* tooltip(int xPos, int yPos) const;

    // Return true if entry is valid to close
    // False if entry is invalid and should be selected and modified (can pop up an error)
    virtual int entryValid() const;
    
    // Used by findWidget to make sure it's returning the actual widget
    // and not a container widget like WidgetScroll
    virtual Widget* returnSelf();
    
    // Changes setting storage (doesn't affect current value if open!)
    void changeStorage(void* newSetting);
    
    // Add to a dialog; if fails, widget is not owned by dialog
    // If successful, dialog will handle deleting widget unless it's not set as wantsToBeDeleted()
    virtual void addTo(Dialog* dialog);
    
    // Set or query tab id
    virtual int stateTabId() const;
    virtual int stateTabId(int wTabId);

    // Selected- perform action
    virtual void doAction();
    // Tabbed to- perform action
    virtual void tabAction();
    
#ifndef NDEBUG
    const char* debugDump() const;
#endif
    
    virtual void disable();
    virtual void enable();

    // Widgets DO want to be deleted if the dialog is
    int wantsToBeDeleted() const;

    // If disabled, refuse all
    virtual int refuseAll() const;

    // Loads setting into our "current" storage
    // Also preps anything else necessary before displaying a dialog, such as
    // setting graphical states back to normal
    virtual void load() = 0;
    
    // Stores our current storage into setting
    virtual void apply() = 0;

    // Called whenever another widget is modified
    // Call AFTER the modification is complete!
    // If this causes us to modify, that won't be propogated
    // Default action is to do nothing
    // A button press doesn't call this
    virtual void siblingModified(Widget* modified);
    
    WindowType windowType() const;
    virtual int getId() const;
    virtual char getShortcut() const;
};

class WButton : public Widget {
protected:
    Dialog::ButtonAction action;
    int command;
    int pressed;
    int spacePressed;
    int hover;

    // Where the icon comes from, if there is one
    int isIcon;
    SDL_Surface* icon;
    int iconX;
    int iconY;
    int iconW;
    int iconH;
    
    enum {
        // Margins on edges (includes bevel)
        GUI_BUTTON_TOPMARGIN = 6,
        GUI_BUTTON_BOTTOMMARGIN = 6,
        GUI_BUTTON_LEFTMARGIN = 12,
        GUI_BUTTON_RIGHTMARGIN = 12,
        // Margin from edge to focus box
        GUI_BUTTON_VFOCUSMARGIN = 4,
        GUI_BUTTON_HFOCUSMARGIN = 5,
        // Margins on edges (includes bevel)
        GUI_ICON_MARGIN = 6,
        // Margin to focus rectangle
        GUI_ICON_FOCUSMARGIN = 4,
    };
    
    // Constructor for radio/checkbox
    WButton(int bId, const std::string& bLabel, void* bSetting);
    WButton(int bId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, void* bSetting);
    
    // Icon display
    void constructIcon(SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH);
    void iconDisplay(SDL_Surface* destSurface, int atX, int atY, int pressed);

public:
    // Buttons do not have a place to store the setting, but instead
    // return a value or perform some other operation; action is a
    // BUTTON constant from Dialog::ButtonAction; command is sent as
    // a message; Buttons with an icon don't currently display a label.
    WButton(int bId, const std::string& bLabel, Dialog::ButtonAction bAction = Dialog::BUTTON_NOTHING, int bCommand = NO_COMMAND);
    WButton(int bId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, Dialog::ButtonAction bAction = Dialog::BUTTON_NOTHING, int bCommand = NO_COMMAND);
    void changeIcon(int iX, int iY);
    // (this won't rearrange any elements)
    virtual void changeText(const std::string& newLabel);
    
    int event(int hasFocus, const SDL_Event* event);
    virtual void load();
    virtual void apply();
    virtual void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
    
    // All button presses go through this function
    virtual void doAction();
    Dialog::ButtonAction getAction() const;
};

class WCheckBox : public WButton {
protected:
    // A checkbox is a bit set to on or off in a single location
    // Only the noted bit should be affected.
    int value;
    int currentStatus;
    int isRadio;
    
    // Other controls enabled or disabled based on button status
    // (control id -> enable or disable)
    std::map<int, int> affectControls;

    enum {
        GUI_CHECKBOX_TOPMARGIN = 1,
        GUI_CHECKBOX_BOTTOMMARGIN = 3,
        GUI_CHECKBOX_LEFTMARGIN = 1,
        GUI_CHECKBOX_RIGHTMARGIN = 4,
        
        // margin before/after checkbox
        GUI_CHECKBOX_PRECHECK = 3,
        GUI_CHECKBOX_POSTCHECK = 7,
    };

    static int checkboxSize;
    static int checkboxX;
    static int checkboxY;
    static int checkboxXText;

public:
    WCheckBox(int cId, const std::string& cLabel, int* cSetting, int cValue);
    WCheckBox(int cId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, int* cSetting, int cValue);
    
    // enable = enable control if checked; otherwise disable if checked
    // doesn't take effect until modified or dialog loaded
    void affectControl(int cId, int enable);
    void changeText(const std::string& newLabel);
    virtual void load();
    virtual void apply();
    void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
    
    // We can query or set the value of the checkbox with this
    // (returns true/false not gaurunteed to be 1/0)
    // ALL places checkbox is checked or modified go through this function
    virtual int state(int checked = -1, int fromLoad = 0);
    virtual void doAction();
    int getValue() const { return value; }
};

// Similar to a checkbox, but only one value can be set at once;
// the location is set to exactly the value- no bit operations are done
// Values must be zero or positive! A value that doesn't match any, will have
// NO button selected when the dialog opens, and will not change it if none of
// them end up selected.
// Setting one radio button unsets all other radio buttons with the same id.
class WRadioButton : public WCheckBox {
public:
    WRadioButton(int rId, const std::string& rLabel, int* rSetting, int rValue);
    WRadioButton(int rId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, int* rSetting, int rValue);
    
    void load();
    void apply();
    
    // We can query or set the value of the radio button with this
    // If we set it, it will unset any other set buttons with the same id.
    // ALL places radio is checked or modified go through this function
    virtual int state(int value = -1, int fromLoad = 0);
    void doAction();
};

class WTextBox : public Widget {
protected:
    enum UndoType {
        UNDO_NONE = 0,
        UNDO_TYPING,
        UNDO_DELETE,
        UNDO_OTHER,
    };
    
    int maxSize;
    std::string currentValue;
    std::string undoValue;
    int undoPoint;
    UndoType undoType;
    
    // Insertion point data- points to the character it is before
    int insertPoint;
    int insertPointX;
    
    // Selection begin OR end will ALWAYS match insertpoint UNLESS
    // there is no selection, in which case both are set to ZERO
    int selectionBegin;
    int selectionEnd;
    
    // Not gaurunteed to be valid if no selection
    int selectionBeginX;
    int selectionEndX;
    
    // How far left we have scrolled the text; 0 if text fully fits into textbox
    // Never a positive value
    int scrollPointX;
    
    // The x/y offset to start display text at normally, accounting for all
    // margins and padding
    int textX;
    int textY;
    
    // Insertion point
    int insertY;
    int insertHeight;
    int insertBlink;
    Uint32 insertBlinkMs;
    
    // Characters that make up "words"
    static const std::string wordChars;

    enum {
        GUI_TEXTBOX_INSERTPOINTWIDTH = 2,
        GUI_TEXTBOX_BORDERTHICKNESS = 2,
        // This is the max (visible) size for a textbox by default
        GUI_TEXTBOX_MAXSIZE = 20,
        // Margin is blank space inside textbox border
        GUI_TEXTBOX_INNERMARGIN = 0,
        // Padding is added to edges of text within textbox, but only shows when
        // fully scrolled to that end of the text
        GUI_TEXTBOX_LEFTPAD = 3,
        GUI_TEXTBOX_RIGHTPAD = 3,
        // Preferred number of pixels to show in excess on each side when scrolling
        GUI_TEXTBOX_SCROLLAHEAD = 10,

        // MS to wait between blinks
        DELAY_CURSOR_BLINK = 500,
    };
    
    // Sets dirty and resets cursor blink
    void setDirtyAndBlink();
    
    // Saves an undo point
    void saveUndo(UndoType type);

public:
    // Text boxes do not have prompts
    // Zero max size = no limit
    WTextBox(int tId, std::string* tSetting, int tMaxSize, int tDisplayLength = -1);
    virtual ~WTextBox();
    
    virtual int event(int hasFocus, const SDL_Event* event);
    virtual void load();
    virtual void apply();
    void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
    void tabAction();
    CommandSupport supportsCommand(int code) const;

    // We can query or set the current text with this; setting text
    // resets selection, insertion, and scroll points; setting text may
    // return different text if the text set was not valid
    const std::string& state() const;
    const std::string& state(const std::string& newValue);
    
    // Returns the insertion position, given a pixel X offset; X offset
    // does not need to be within the actual display boundaries (IE you can drag
    // past the edges)
    int insertWhere(int x) const;
    
    // Handle special keystrokes
    void keyBackspace();
    void keyDelete();
    
    // Query or set the insertion point, which also clears selection and
    // scrolls if needed to view; if drag is true, this drags the current
    // insertion point to the given position, modifying the selection as well
    int insertionState(int newPos = -1, int drag = 0);

    // Selects entire text, scrolls to end
    void selectAll();
    
    // Undo
    void undo();
    
    // Inserts text at the insertion point, overwriting any selection
    // No text = clear selection
    // Resets selection and adjusts insertion and scroll points
    void pasteText(const std::string& text);
    
    // Copies current selection to buffer
    // Copies "" if no selection
    void copyText(std::string& buffer) const;

    // Validates a change to textbox before it occurs, returns true if change
    // is OK; making changes to new text or cursor position is OK; this is only
    // called if text changes, not just if cursor position does; you can assume
    // new cursor position is right after the added text or where the deleted
    // text was. Default version allows all changes. Isn't called just on cursor
    // move or load().
    virtual int allowChange(const std::string& oldValue, std::string* newValue,
                            int oldCursorPos, int* newCursorPos) const;
};

class WNumberBox : public WTextBox {
protected:
    int min;
    int max;

public:
    WNumberBox(int tId, int* tSetting, int tMin, int tMax);
    ~WNumberBox();
    
    int allowChange(const std::string& oldValue, std::string* newValue,
                    int oldCursorPos, int* newCursorPos) const;

    int event(int hasFocus, const SDL_Event* event);
    void load();
    void apply();
    int entryValid() const;

    int state() const;
    int state(int newValue);
};

class WStatic : public Widget {
public:
    enum {
        REFUSE_STATIC = 2,
    };

    // Static text does not have a place to store any setting
    WStatic(int sId, const std::string& sLabel);
    void changeText(const std::string& newLabel);

    // Refuse all!
    // If not disabled, returns WStatic::REFUSE_STATIC to clarify
    int refuseAll() const;
    
    int event(int hasFocus, const SDL_Event* event);
    void load();
    void apply();
    void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
};

#endif

